Version 1.0 | First Created Nov 4, 2023 | Updated Nov 15, 2023

Keywords: Transportation Planning, Rideshare Forecast, Spatial and Serial Autocorrelation, Nested Tibble, OLS Regression, time lag, gganimate, purrr

Introduction

Bikeshare programs have become an integral part of urban transportation system, providing a sustainable and convenient alternative to traditional commuting methods. Citi Bike is one of the earliest bicycle sharing system created by Lyft in 2008 at NYC. But in recent years, this program also extends to Jersey City across the Hudson River in order to cater to the dynamic mobility needs of residents who had to commute to Manhattan for work. As the popularity of Citi Bike continues to soar, there arises a pressing need for effective management strategies to ensure the availability of bikes at each station in Jersey City.

Bikesharing (and ridesharing) inherently involves the challenge of maintaining a delicate equilibrium in bike distribution, ie. “re-balancing”. This issue arises from the spatial and temporal variations in user demand. If certain stations experience a surge in demand during morning rush hours while others witness a decline, this could result in station clusters with depleted or surplus bike stocks, thereby compromising user experience. Companies like Uber & Lyft generate and analyze tremendous amounts of data to incentivize bike (ride) share use, solve routing problems, calculate pricing, and obviously forecast demands, but they are not very transparent about the algorithm they use. Therefore, the goal of this report is keep a simple, but open source version of bikeshare demand prediction, by harnessing of power of machine learning, particularly the Ordinary Least Square (OLS) regression to rapidly predict the demand for bikes across various stations in Jersey City. We will perform analysis on the historical data on bike usage at different stations and different time as well as under different weather conditions. The forecast allows the Citi Bike program to proactively address imbalances, strategically re-positioning bikes in anticipation of peak usage time or special events.

The subsequent sections on this report is organized as follows. Firstly, we load in our datasets for a quick examinations and perform some necessary feature engineering. Secondly, we conduct exploratory analysis on the temporal and spatial aspects of bikeshare data. We will also look at some weather data in this process. Thirdly, we create a space-time panel where each row is a unique instance of time-space combination of rides and visualize the correlation between space, time, weather, and number of rides. And finally, we make created four different models, test their sensibility to errors, and conduct cross validation.

The GitHub repository for this report is here.

Set Up

The data we use here is obtained from NYC OpenData that contains the latest record of Citi Bike usage on a monthly base. Note that while Jersey City is located in New Jersey, Citi Bike is mainly operated in NYC and therefore its dataset is also operated by NYC. We selected all bikeshare data in Jersey City from Aug 1 2023 to Aug 31 2023 as our temporal frame. The dataset mainly includes locations and names of stations of which the user started the ride as well as end the ride. In addition, we also downloaded the census tract geometries of the entire Hudson County via tidycensus, which includes Jersey City.

nycbike <- read.csv(url("https://raw.githubusercontent.com/emilyzhou112/MUSA5080-NJ-BikeShare/main/JC-202308-citibike-tripdata.csv"))

njCensus <- get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2021, 
          state = "NJ", 
          geometry = TRUE, 
          county=c("Hudson"),
          output = "wide") %>%
  rename(Total_Pop =  B01003_001E,
         Med_Inc = B19013_001E,
         Med_Age = B01002_001E,
         White_Pop = B02001_002E,
         Travel_Time = B08013_001E,
         Num_Commuters = B08012_001E,
         Means_of_Transport = B08301_001E,
         Total_Public_Trans = B08301_010E) %>%
  select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
         Means_of_Transport, Total_Public_Trans,
         Med_Age,
         GEOID, geometry) %>%
  mutate(Percent_White = White_Pop / Total_Pop,
         Mean_Commute_Time = Travel_Time / Total_Public_Trans,
         Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)

jcTracts <- njCensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  select(GEOID, geometry) %>% 
  st_sf()

Since our raw bikeshare data is a non-spatial layer, despite containing spatial attributes, our next step is to make it spatial by joining it to the census geometry. We are only interested in the start location of each ride because we want to predict the capacity of each station when the user need a bike to start the ride. This requires us to filter out rows with missing latitude and longitude. We also found that some stations have erroneous longitude and latitude coding that lead to these points being located in the Hudson River. These points also need to be removed.

bike_census <- st_join(nycbike %>% 
                         filter(is.na(start_lat) == FALSE &
                                is.na(start_lng) == FALSE &
                                is.na(end_lat) == FALSE &
                                is.na(end_lng) == FALSE) %>%
                         st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326),
                       jcTracts %>%  st_transform(crs=4326),
                       join=st_intersects, left = TRUE) %>%
  rename(Origin.Tract = GEOID) %>%
  mutate(start_lng = unlist(map(geometry, 1)),
         start_lat = unlist(map(geometry, 2)))%>%
  filter(!(ride_id %in% c("BEC9CC4D1007C753", "86087F431785EDE7", "A1AAB92631439883", "71938DA085242DCC", "4203C77668C47C75", "C92410CA2AEF3947", "6E02CBF36C90F244", "0259885253FD238E", "B39136B37AFB5FE9", "1EC9376D1559C51C", "CDDF16D3A37BE370"))) %>% 
  as.data.frame()

The following visualization shows all the steps we did above.

ggplot()+
  geom_sf(data = jcTracts %>%
          st_transform(crs=4326), fill="white")+
  geom_point(data = bike_census, aes(x=start_lng, y = start_lat), 
            color = "#6E0E0A", alpha = 1, size = 0.3) + 
  ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  labs(title = "Location of Rides Starting Points in Jersey City") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

We will perform some basic feature engineering using R’s powerful lubridate package to extract some temporal information for each ride. This includes calculating the 60min and 15min interval for the starting time of each ride, and categorizing these intervals into by the time of day into “Overnight”, “AM Rush”, “Mid-Day”, and “PM Rush” and by day of week into weekday and weekend.

bike_census <- bike_census %>%
  mutate(interval60 = floor_date(ymd_hms(started_at), unit = "hour"),
         interval15 = floor_date(ymd_hms(started_at), unit = "15 mins"),
         week = week(interval60),
         dotw = wday(interval60, label=TRUE)) %>% 
  mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>% 
  mutate(hour = hour(started_at),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"))

Interested in seeing the commuting patterns to get a sense of rider’s daily behavior, we created line strings that connect between the start and end locations of each ride. For the simplicity of visualization, we only visualize 200 rides. We may see that most of the rides occur within Jersey City, but there are also people who commute using Citi Bike from Jersey City to Manhattan.

lines <- st_sfc(
  lapply(1:nrow(bike_census), function(i) {
    st_linestring(matrix(c(bike_census[i, "start_lng"], bike_census[i, "start_lat"],
                            bike_census[i, "end_lng"], bike_census[i, "end_lat"]), ncol = 2, byrow = TRUE))
  }))

lines_sf <- st_sf(geometry = lines)
lines_sf <- st_set_crs(lines_sf, 4326)

Route <- lines_sf %>% head(200)
mapview(Route, zcol = NULL, color = "#6E0E0A", linewidth = 0.001, alpha = 0.3) # only show 200

Exploratory Analysis

In this section, we delve into some further exploratory analysis of our data set.

Temporal Analysis

Starting with temporal analysis, we first visualized the bikeshare trips per hour in Jersey City in August 2023. It is clear that there’s some form of circularity in that there are both peak hours and non-peak hours during a single day. Also, we may see that five consecutive higher peaks are usually followed by two less higher peaks. These correspond to high bikeshare need during rush hours and low bikeshare needs overnight, in early mornings, and over the weekend

ggplot(bike_census %>%
         group_by(interval60) %>%
         tally())+
  geom_line(aes(x = interval60, y = n), color= "#6E0E0A")+
  labs(title="Bike Share Trips Per Hour in Jersey City, Aug 2023",
       x="Date", 
       y="Number of trips") + 
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
## Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Then, we visualized the distribution of trips by stations at each hour in August. We may see that the distribution of rides is highly right skewed, with most stations having zero or a few number of trips each hour and a couple of stations having much more rides, up to 40 or even 50 rides at a particular hour on a particular day in August 2023.

ggplot(bike_census %>%
         group_by(interval60, start_station_name) %>%
         tally())+
  geom_histogram(aes(n), binwidth = 5, fill="#6E0E0A")+
  labs(title="Bike Share Trips Per Hour by Station in Jersey City, Aug 2023",
       x="Trip Counts", 
       y="Number of Stations") + 
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

We continue to visualize the distribution of mean number of trips at each station during different times of the day. We recognize that during PM rush, the average number of trips has been the highest throughout, with a couple of stations’ average trip exceed 10, implying a relatively high demand for a single station. This is followed by ride demand in mid day, when some stations can have more than 5 rides. It is also surprising to notice that the demand is pretty high overnight, with a few stations having more than 5 rides.

bike_census %>%
        group_by(interval60, start_station_name, time_of_day) %>%
         tally()%>%
  group_by(start_station_name, time_of_day)%>%
  summarize(mean_trips = mean(n))%>%
  ggplot()+
  geom_histogram(aes(mean_trips, fill=time_of_day), binwidth = 1)+
  scale_fill_manual(values = palette4, name="Time of Day") + 
  labs(title="Mean Number of Hourly Trips Per Station",
       x="Number of trips", 
       y="Frequency")+
  facet_wrap(~time_of_day) +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

After that, we visualized the total number of trips in August separated by day of the week. The two main patterns here are that firstly, trip numbers vary by time of the day. Lowest trip counts occur in early morning while highest trip count occur in late afternoon. Secondly, Tuesday, Wednesday, and Thursday tend to observe higher number of trips.

ggplot(bike_census %>% mutate(hour = hour(started_at)))+
     geom_freqpoly(aes(hour, color = dotw), binwidth = 3, lwd = 0.8)+
  scale_color_manual(values = c("#124E78", "#819FA1", "#F0F0C9", "#F2BB05", "#E58507", "#D74E09", "#6E0E0A"), name = "Day") + 
  labs(title="Bike Share Trips in Jersey City by Day of the Week, Aug 2023",
       x="Hour", 
       y="Trip Counts") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Same type of visualize but this time separated between weekend and weekdays. It is very obvious that we see more trips during weekdays and less trips during weekends. Also, during weekends, number of trips start to increase much later than during weekdays, suggesting the missing of morning rush hour will significantly impact people’s use of bikeshare services.

ggplot(bike_census)+
     geom_freqpoly(aes(hour, color = weekend), binwidth = 3, lwd = 1.2)+
  scale_color_manual(values=palette2) + 
  labs(color = "Time of Week") +
  labs(title="Bike Share Trips in Jersey City - Weekend vs Weekday, Aug 2023",
       x="Hour", 
       y="Trip Counts") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Building onto that, we group this dataset by station and time of day to visualize the total number of trips at each station during a specific period of time on weekday or weekend. To simplify our visualization, we selected the top 50 occurrences in our dataset. Note that here, each bar represent an instance of station-time of day-weekday/weekend combination. We are able to see that PM rush hour indeed sees that greatest bikeshare demand for a couple of stations in particular. Weekend rides only appear three times in the top 50 list. Zooming into the detail reveals to us that Grove St PATH station observes a total of 1117 rides during PM rush on a weekday, 801 rides overnight on a weekday in August 2023. South Waterfront Walkway - Sinatra Dr & 1 St station observes 319 rides overnight on a weekend and 731 rides during PM rush on a weekday in August 2023.

to_plot <- bike_census%>% # only show 100 stations
  group_by(start_station_id, start_lat, start_lng, weekend, time_of_day, start_station_name) %>%
  tally() %>% 
  arrange(-n) %>% 
  head(50) 
to_plot$ID <- seq_along(to_plot$start_station_id)

to_plot %>% 
  arrange(-n) %>%
  head(50) %>% 
  ggplot(aes(x = reorder(ID, -n), n, fill = time_of_day, color = weekend)) +
  scale_fill_manual(values = palette4, name="Time of Day") + 
  guides(color="none") + 
  geom_bar(stat = "identity", position="stack") +
  scale_color_manual(values = c("transparent", "black")) +
  labs(title="Top 50 Occurences of Rides By Time")+
  ylab("Number of Rides") +
  xlab("") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Spatial Analysis

ggplot()+
  geom_sf(data = jcTracts %>%
          st_transform(crs=4326), fill = "white")+
  geom_point(data = to_plot,
             aes(x=start_lng, y = start_lat, size=n), color = alpha("#6E0E0A", 0.7)) + 
  ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  scale_size(range = c(0.5, 8)) + 
  labs(size = "Number of Rides") +
  facet_grid(weekend ~ time_of_day) + 
    labs(title="Locations of Stations with Greatest Bikeshare Demand by Time")+ 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

moran_data <- bike_census %>%
  group_by(start_station_id, start_lat, start_lng, start_station_name) %>%
  tally() %>% 
  arrange(-n) %>% 
  head(80) %>% 
  st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326)


coords <-  st_coordinates(moran_data) 
neighborList <- knn2nb(knearneigh(coords, 5))
spatialWeights <- nb2listw(neighborList, style="W")

moranTest <- moran.mc(moran_data$n, 
                      spatialWeights, nsim = 999)

ggplot(as.data.frame(moranTest$res[c(1:999)]), aes(moranTest$res[c(1:999)])) +
  geom_histogram(binwidth = 0.01, fill = "#124E78") +
  geom_vline(aes(xintercept = moranTest$statistic), colour = "#6E0E0A" ,lwd=1) +
  scale_x_continuous(limits = c(-0.5, 0.5)) +
  labs(title = "Observed and Permuted Moran's I for Stations with Most Rides",
       subtitle = "Observed Moran's I in Red",
       x = "Moran's I",
       y = "Count") +
  theme_light() +   
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10))

  ggplot() +
  geom_sf(data = jcTracts, fill = "white") +
  geom_sf(data = moran_data,
          aes(size = n, color = n)) + 
  ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) + 
  scale_size(
    range = c(1, 6),
    guide = guide_legend(
      direction = "horizontal",
      nrow = 1,
      label.position = "bottom")) +
  scale_color_gradientn(colours = hcl.colors(5, "RdBu",rev = TRUE, alpha = 0.9), 
                        guide = guide_legend(direction = "horizontal", nrow = 1)) +
  labs(title = "Stations with Most Rides",
       subtitle =  "Dot size proportional to rides") + 
  theme(legend.position = "bottom") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

week33 <-
  filter(bike_census , week == 33 & dotw == "Mon")

week33.panel <-
  expand.grid(
    interval15 = unique(week33$interval15),
    Origin.Tract = unique(bike_census$Origin.Tract))

bike.animation.data <-
  mutate(week33, Trip_Counter = 1) %>%
    right_join(week33.panel) %>% 
    group_by(interval15, Origin.Tract) %>%
    summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>% 
    ungroup() %>% 
    left_join(jcTracts, by=c("Origin.Tract" = "GEOID")) %>%
    st_sf() %>%
    mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
                             Trip_Count > 0 & Trip_Count <= 3 ~ "1-3 trips",
                             Trip_Count > 3 & Trip_Count <= 6 ~ "4-6 trips",
                             Trip_Count > 6 & Trip_Count <= 10 ~ "7-10 trips",
                             Trip_Count > 10 ~ "11+ trips")) %>%
    mutate(Trips  = fct_relevel(Trips, "0 trips","1-3 trips","4-6 trips",
                                       "7-10 trips","10+ trips"))

bikeshare_animation <-
  ggplot() +
    geom_sf(data = bike.animation.data, aes(fill = Trips)) +
    scale_fill_manual(values = palette5) +
    labs(title = "Bikeshare For One Day in Aug 2023",
         subtitle = "15 minute intervals: {current_frame}") +
    transition_manual(interval15)  + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )
  

#animate(bikeshare_animation, duration=40, renderer = gifski_renderer())

anim_save("animation.gif", animation = bikeshare_animation, end_pause = 4, height = 1800, width = 3000, fps = 6, renderer = gifski_renderer())

animate

Weather Analysis

weather.Panel <- 
  riem_measures(station = "EWR", date_start = "2023-08-01", date_end = "2023-09-01") %>%
  dplyr::select(valid, tmpf, p01i, sknt)%>%
  replace(is.na(.), 0) %>%
    mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
    mutate(week = week(interval60),
           dotw = wday(interval60, label=TRUE)) %>%
    group_by(interval60) %>%
    summarize(Temperature = max(tmpf),
              Precipitation = sum(p01i),
              Wind_Speed = max(sknt)) %>%
    mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))
grid.arrange(
  ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line(color="#6E0E0A") + 
    labs(title="Percipitation", x="Hour", y="Perecipitation") + 
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank()), 
  ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line(color="#D74E09") + 
    labs(title="Wind Speed", x="Hour", y="Wind Speed") +
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank()),  
  ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line(color="#124E78") + 
    labs(title="Temperature", x="Hour", y="Temperature") +theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank()), 
  top="Weather Data ")

Everything below needs re-run

Space-Time Panel

study.panel <- 
  expand.grid(interval60=unique(bike_census$interval60), 
              start_station_id = unique(bike_census$start_station_id)) %>%
  left_join(., bike_census %>%
              select(start_station_id, start_station_name, Origin.Tract, start_lng, start_lat, rideable_type)%>%
              distinct() %>%
              group_by(start_station_id) %>%
              slice(1))
bike.panel <- 
  bike_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, start_station_id, start_station_name, Origin.Tract, start_lng, start_lat) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(is.na(start_station_id) == FALSE) %>%
  mutate(week = week(interval60),
         dotw = wday(interval60, label = TRUE)) %>%
  filter(is.na(Origin.Tract) == FALSE) %>% 
  left_join(., njCensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))

Weather Correlation

bike.panel %>%
  group_by(interval60) %>% 
  summarize(Trip_Count = mean(Trip_Count),
            Temperature = first(Temperature)) %>%
  mutate(week = week(interval60)) %>%
  ggplot(aes(Temperature, Trip_Count)) + 
    geom_point(color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
    facet_wrap(~week, ncol=8) + 
    labs(title="Trip Count As a Fuction of Temperature by Week",
         x="Temperature", y="Mean Trip Count") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

bike.panel %>%
  group_by(interval60) %>% 
  summarize(Trip_Count = mean(Trip_Count),
            Wind = first(Wind_Speed)) %>%
  mutate(week = week(interval60)) %>%
  ggplot(aes(Wind, Trip_Count)) + 
    geom_point(color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
    facet_wrap(~week, ncol=8) + 
    labs(title="Trip Count As a Fuction of Wind by Week",
         x="Wind Speed", y="Mean Trip Count") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)) + 
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Serial Correlation

bike.panel <- 
  bike.panel %>% 
  arrange(start_station_id, interval60) %>% 
  mutate(lagHour = dplyr::lag(Trip_Count,1),
         lag2Hours = dplyr::lag(Trip_Count,2),
         lag3Hours = dplyr::lag(Trip_Count,3),
         lag4Hours = dplyr::lag(Trip_Count,4),
         lag12Hours = dplyr::lag(Trip_Count,12),
         lag1day = dplyr::lag(Trip_Count,24)) %>%
   mutate(day = yday(interval60))
as.data.frame(bike.panel) %>%
    group_by(interval60) %>% 
    summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
    gather(Variable, Value, -interval60, -Trip_Count) %>%
  ggplot(aes(x = Value , y = Trip_Count)) +
    geom_point(size = 1, color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
  facet_wrap(~Variable, ncol = 6)+
  labs(x = "Variable", y = "Correlation", title = "Correlation Between Serial Lag Trips and Trip Count") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Spatial Correlation

lag_result <- data.frame()

for (d in 213:243) {
  for (h in 0:23) {

spacelag <- bike.panel %>% 
  mutate(hour = hour(interval60)) %>% 
  filter(day == d & hour == h) %>% 
  st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326) 


coords.test_lag <-  st_coordinates(spacelag) 
neighborList.test_lag <- knn2nb(knearneigh(coords.test_lag, 5))
spatialWeights.test_lag <- nb2listw(neighborList.test_lag, style="W")

spacelag <- spacelag %>% mutate(lagTrip = lag.listw(spatialWeights.test_lag, Trip_Count))

lag_result <- rbind(lag_result, spacelag)
  }
  
}

bike.panel <- lag_result %>% 
  mutate(start_lng = unlist(map(geometry, 1)),
         start_lat = unlist(map(geometry, 2))) %>% 
 st_drop_geometry() 
bike.panel %>%
  group_by(interval60) %>% 
  summarize(Trip_Count = mean(Trip_Count),
            Lag_Count = mean(lagTrip)) %>%
  mutate(week = week(interval60)) %>%
  ggplot(aes(Lag_Count, Trip_Count)) + 
    geom_point(size = 1, color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
    facet_wrap(~week, ncol=8) + 
    labs(title="Correlation Between Spatial Lag Trips and Trip Count",
         x="Lag Trip Count", y="Mean Trip Count") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

OLS Regression

bike.Train <- filter(bike.panel, week <= 33)
bike.Test <- filter(bike.panel, week > 33)

# Time
reg1 <- 
  lm(Trip_Count ~  hour(interval60) + dotw + Temperature + Wind_Speed, data=bike.Train)

# Space
reg2 <- 
  lm(Trip_Count ~  start_station_name + dotw + Temperature + Wind_Speed,  data=bike.Train)

# Time and Space
reg3 <- 
  lm(Trip_Count ~  start_station_name + hour(interval60) + dotw + Temperature + Wind_Speed, 
     data=bike.Train)

# Time and Space and TimeLag and SpaceLag
reg4 <- 
  lm(Trip_Count ~  start_station_name +  hour(interval60) + dotw + Temperature + Wind_Speed +
                   lagHour + lag2Hours + lag12Hours + lag1day + lagTrip, 
     data=bike.Train)

Making Predictions

bike.Test.weekNest <- 
  bike.Test %>%
  nest(-week) 
model_pred <- function(dat, fit){
   pred <- predict(fit, newdata = dat)}
week_predictions <- 
  bike.Test.weekNest %>% 
    mutate(ATime_FE = map(.x = data, fit = reg1, .f = model_pred),
           BSpace_FE = map(.x = data, fit = reg2, .f = model_pred),
           CTime_Space_FE = map(.x = data, fit = reg3, .f = model_pred),
           DTime_Space_FE_Lags = map(.x = data, fit = reg4, .f = model_pred)) %>% 
    gather(Regression, Prediction, -data, -week) %>%
    mutate(Observed = map(data, pull, Trip_Count),
           Absolute_Error = map2(Observed, Prediction,  ~ abs(.x - .y)),
           MAE = map_dbl(Absolute_Error, mean, na.rm = TRUE),
           sd_AE = map_dbl(Absolute_Error, sd, na.rm = TRUE))

Error Analysis

week_predictions %>%
  dplyr::select(week, Regression, MAE) %>%
  gather(Variable, MAE, -Regression, -week) %>%
  ggplot(aes(week, MAE)) + 
    geom_bar(aes(fill = Regression), position = "dodge", stat="identity") +
    scale_fill_manual(values = palette4) +
    labs(title = "Mean Absolute Errors by Model Specification and Week") +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station_id = map(data, pull, start_station_id)) %>%
    dplyr::select(interval60, start_station_id, Observed, Prediction, Regression) %>%
    unnest() %>%
    gather(Variable, Value, -Regression, -interval60, -start_station_id) %>%
    group_by(Regression, Variable, interval60) %>%
    summarize(Value = sum(Value)) %>%
    ggplot(aes(interval60, Value, colour=Variable)) + 
      geom_line(size = 1.1) + 
      scale_color_manual(values=palette2)+
      facet_wrap(~Regression, ncol=1) +
      labs(title = "Predicted/Observed Bike Share Time Series", subtitle = "Jersey City: a test set of 2 weeks",  x = "Hour", y= "Station Trips") + 
   theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station_id = map(data, pull, start_station_id), 
           start_latitude = map(data, pull, start_lat), 
           start_longitude = map(data, pull, start_lng)) %>%
    select(interval60, start_station_id, start_longitude, start_latitude, Observed, Prediction, Regression) %>%
    unnest() %>%
  filter(Regression == "DTime_Space_FE_Lags") %>%
  group_by(start_station_id, start_longitude, start_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
  geom_sf(data = jcTracts,  fill = "white")+
  geom_point(aes(x = start_longitude, y = start_latitude, color = MAE), 
             fill = "transparent")+
  scale_color_continuous(low = "#124E78", high = "#6E0E0A", name= "MAE")+
 ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  labs(title="Mean Abs Error, Test Set, Model 4") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station_id = map(data, pull, start_station_id), 
           start_latitude = map(data, pull, start_lat), 
           start_longitude = map(data, pull, start_lng),
           dotw = map(data, pull, dotw) ) %>%
    select(interval60, start_station_id, start_longitude, 
           start_latitude, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "DTime_Space_FE_Lags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
  group_by(start_station_id, weekend, time_of_day, start_longitude, start_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  ggplot(.)+
  geom_sf(data = jcTracts, color = "grey", fill = "transparent")+
  geom_point(aes(x = start_longitude, y = start_latitude, color = MAE), 
             fill = "transparent", size = 0.5)+
  scale_color_continuous(low = "#124E78", high = "#6E0E0A", name= "MAE")+
 ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  facet_grid(weekend~time_of_day)+
  labs(title="Mean Absolute Errors by Time, Test Set, Model 4") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

Cross-Validation

fitControl <- trainControl(method = "cv", number = 100)

reg.cv <- train(Trip_Count ~ start_station_name +  hour(interval60) + dotw + Temperature + Wind_Speed + lagHour + lag2Hours + lag12Hours + lag1day + lagTrip, data=bike.panel, method = "lm", trControl = fitControl, na.action = na.pass)

reg.cv$resample %>% 
  summarise(MAE = mean(reg.cv$resample[,3]),
            sd(reg.cv$resample[,3])
) %>%
  kbl(col.name=c('Mean Absolute Error','Standard Deviation of MAE')) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Mean Absolute Error Standard Deviation of MAE
0.3696923 0.0254276

Conclusion

LS0tCnRpdGxlOiAiUHJlZGljdGluZyBTdW1tZXIgQmlrZXNoYXJlIERlbWFuZCBpbiBKZXJzZXkgQ2l0eSIKYXV0aG9yOiAiRW1pbHkgWmhvdSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBzaW1wbGV4CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogeWVzCmVkaXRvcl9vcHRpb25zOgogIG1hcmtkb3duOgogICAgd3JhcDogc2VudGVuY2UKLS0tCgpWZXJzaW9uIDEuMCBcfCBGaXJzdCBDcmVhdGVkIE5vdiA0LCAyMDIzIFx8IFVwZGF0ZWQgTm92IDE1LCAyMDIzCgpLZXl3b3JkczogVHJhbnNwb3J0YXRpb24gUGxhbm5pbmcsIFJpZGVzaGFyZSBGb3JlY2FzdCwgU3BhdGlhbCBhbmQgU2VyaWFsIEF1dG9jb3JyZWxhdGlvbiwgTmVzdGVkIFRpYmJsZSwgT0xTIFJlZ3Jlc3Npb24sIHRpbWUgbGFnLCBnZ2FuaW1hdGUsIHB1cnJyCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgoKIyBJbnRyb2R1Y3Rpb24KCkJpa2VzaGFyZSBwcm9ncmFtcyBoYXZlIGJlY29tZSBhbiBpbnRlZ3JhbCBwYXJ0IG9mIHVyYmFuIHRyYW5zcG9ydGF0aW9uIHN5c3RlbSwgcHJvdmlkaW5nIGEgc3VzdGFpbmFibGUgYW5kIGNvbnZlbmllbnQgYWx0ZXJuYXRpdmUgdG8gdHJhZGl0aW9uYWwgY29tbXV0aW5nIG1ldGhvZHMuIENpdGkgQmlrZSBpcyBvbmUgb2YgdGhlIGVhcmxpZXN0IGJpY3ljbGUgc2hhcmluZyBzeXN0ZW0gY3JlYXRlZCBieSBMeWZ0IGluIDIwMDggYXQgTllDLiBCdXQgaW4gcmVjZW50IHllYXJzLCB0aGlzIHByb2dyYW0gYWxzbyBleHRlbmRzIHRvIEplcnNleSBDaXR5IGFjcm9zcyB0aGUgSHVkc29uIFJpdmVyIGluIG9yZGVyIHRvIGNhdGVyIHRvIHRoZSBkeW5hbWljIG1vYmlsaXR5IG5lZWRzIG9mIHJlc2lkZW50cyB3aG8gaGFkIHRvIGNvbW11dGUgdG8gTWFuaGF0dGFuIGZvciB3b3JrLiBBcyB0aGUgcG9wdWxhcml0eSBvZiBDaXRpIEJpa2UgY29udGludWVzIHRvIHNvYXIsIHRoZXJlIGFyaXNlcyBhIHByZXNzaW5nIG5lZWQgZm9yIGVmZmVjdGl2ZSBtYW5hZ2VtZW50IHN0cmF0ZWdpZXMgdG8gZW5zdXJlIHRoZSBhdmFpbGFiaWxpdHkgb2YgYmlrZXMgYXQgZWFjaCBzdGF0aW9uIGluIEplcnNleSBDaXR5LiAKCkJpa2VzaGFyaW5nIChhbmQgcmlkZXNoYXJpbmcpIGluaGVyZW50bHkgaW52b2x2ZXMgdGhlIGNoYWxsZW5nZSBvZiBtYWludGFpbmluZyBhIGRlbGljYXRlIGVxdWlsaWJyaXVtIGluIGJpa2UgZGlzdHJpYnV0aW9uLCBpZS4gInJlLWJhbGFuY2luZyIuIFRoaXMgaXNzdWUgYXJpc2VzIGZyb20gdGhlIHNwYXRpYWwgYW5kIHRlbXBvcmFsIHZhcmlhdGlvbnMgaW4gdXNlciBkZW1hbmQuIElmIGNlcnRhaW4gc3RhdGlvbnMgZXhwZXJpZW5jZSBhIHN1cmdlIGluIGRlbWFuZCBkdXJpbmcgbW9ybmluZyBydXNoIGhvdXJzIHdoaWxlIG90aGVycyB3aXRuZXNzIGEgZGVjbGluZSwgdGhpcyBjb3VsZCByZXN1bHQgaW4gc3RhdGlvbiBjbHVzdGVycyB3aXRoIGRlcGxldGVkIG9yIHN1cnBsdXMgYmlrZSBzdG9ja3MsIHRoZXJlYnkgY29tcHJvbWlzaW5nIHVzZXIgZXhwZXJpZW5jZS4gQ29tcGFuaWVzIGxpa2UgVWJlciAmIEx5ZnQgZ2VuZXJhdGUgYW5kIGFuYWx5emUgdHJlbWVuZG91cyBhbW91bnRzIG9mIGRhdGEgdG8gaW5jZW50aXZpemUgYmlrZSAocmlkZSkgc2hhcmUgdXNlLCBzb2x2ZSByb3V0aW5nIHByb2JsZW1zLCBjYWxjdWxhdGUgcHJpY2luZywgYW5kIG9idmlvdXNseSBmb3JlY2FzdCBkZW1hbmRzLCBidXQgdGhleSBhcmUgbm90IHZlcnkgdHJhbnNwYXJlbnQgYWJvdXQgdGhlIGFsZ29yaXRobSB0aGV5IHVzZS4gVGhlcmVmb3JlLCB0aGUgZ29hbCBvZiB0aGlzIHJlcG9ydCBpcyBrZWVwIGEgc2ltcGxlLCBidXQgb3BlbiBzb3VyY2UgdmVyc2lvbiBvZiBiaWtlc2hhcmUgZGVtYW5kIHByZWRpY3Rpb24sIGJ5IGhhcm5lc3Npbmcgb2YgcG93ZXIgb2YgbWFjaGluZSBsZWFybmluZywgcGFydGljdWxhcmx5IHRoZSBPcmRpbmFyeSBMZWFzdCBTcXVhcmUgKE9MUykgcmVncmVzc2lvbiB0byByYXBpZGx5IHByZWRpY3QgdGhlIGRlbWFuZCBmb3IgYmlrZXMgYWNyb3NzIHZhcmlvdXMgc3RhdGlvbnMgaW4gSmVyc2V5IENpdHkuIFdlIHdpbGwgcGVyZm9ybSBhbmFseXNpcyBvbiB0aGUgaGlzdG9yaWNhbCBkYXRhIG9uIGJpa2UgdXNhZ2UgYXQgZGlmZmVyZW50IHN0YXRpb25zIGFuZCBkaWZmZXJlbnQgdGltZSBhcyB3ZWxsIGFzIHVuZGVyIGRpZmZlcmVudCB3ZWF0aGVyIGNvbmRpdGlvbnMuIFRoZSBmb3JlY2FzdCBhbGxvd3MgdGhlIENpdGkgQmlrZSBwcm9ncmFtIHRvIHByb2FjdGl2ZWx5IGFkZHJlc3MgaW1iYWxhbmNlcywgc3RyYXRlZ2ljYWxseSByZS1wb3NpdGlvbmluZyBiaWtlcyBpbiBhbnRpY2lwYXRpb24gb2YgcGVhayB1c2FnZSB0aW1lIG9yIHNwZWNpYWwgZXZlbnRzLiAKClRoZSBzdWJzZXF1ZW50IHNlY3Rpb25zIG9uIHRoaXMgcmVwb3J0IGlzIG9yZ2FuaXplZCBhcyBmb2xsb3dzLiBGaXJzdGx5LCB3ZSBsb2FkIGluIG91ciBkYXRhc2V0cyBmb3IgYSBxdWljayBleGFtaW5hdGlvbnMgYW5kIHBlcmZvcm0gc29tZSBuZWNlc3NhcnkgZmVhdHVyZSBlbmdpbmVlcmluZy4gU2Vjb25kbHksIHdlIGNvbmR1Y3QgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb24gdGhlIHRlbXBvcmFsIGFuZCBzcGF0aWFsIGFzcGVjdHMgb2YgYmlrZXNoYXJlIGRhdGEuIFdlIHdpbGwgYWxzbyBsb29rIGF0IHNvbWUgd2VhdGhlciBkYXRhIGluIHRoaXMgcHJvY2Vzcy4gVGhpcmRseSwgd2UgY3JlYXRlIGEgc3BhY2UtdGltZSBwYW5lbCB3aGVyZSBlYWNoIHJvdyBpcyBhIHVuaXF1ZSBpbnN0YW5jZSBvZiB0aW1lLXNwYWNlIGNvbWJpbmF0aW9uIG9mIHJpZGVzIGFuZCB2aXN1YWxpemUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gc3BhY2UsIHRpbWUsIHdlYXRoZXIsIGFuZCBudW1iZXIgb2YgcmlkZXMuIEFuZCBmaW5hbGx5LCB3ZSBtYWtlIGNyZWF0ZWQgZm91ciBkaWZmZXJlbnQgbW9kZWxzLCB0ZXN0IHRoZWlyIHNlbnNpYmlsaXR5IHRvIGVycm9ycywgYW5kIGNvbmR1Y3QgY3Jvc3MgdmFsaWRhdGlvbi4gCgpUaGUgR2l0SHViIHJlcG9zaXRvcnkgZm9yIHRoaXMgcmVwb3J0IGlzIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vZW1pbHl6aG91MTEyL01VU0E1MDgwLU5KLUJpa2VTaGFyZSkuCgojIFNldCBVcAoKYGBge3IgbGlicmFyeSBhbmQgZGF0YSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHRpZ3JpcykKbGlicmFyeShnZ2FuaW1hdGUpCmxpYnJhcnkocmllbSkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KG1hcHZpZXcpCmxpYnJhcnkoc2ZkZXApCmxpYnJhcnkoc3BkZXApCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkgKHJlYWRyKQoKb3B0aW9ucyh0aWdyaXNfY2xhc3MgPSAic2YiKQpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS91cmJhblNwYXRpYWwvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvZnVuY3Rpb25zLnIiKQoKcGFsZXR0ZTUgPC0gYygiIzEyNEU3OCIsIiNGMEYwQzkiLCIjRjJCQjA1IiwiI0Q3NEUwOSIsIiM2RTBFMEEiKQpwYWxldHRlNCA8LSBjKCIjMTI0RTc4IiwiI0YwRjBDOSIsIiNGMkJCMDUiLCIjNkUwRTBBIikKcGFsZXR0ZTIgPC0gYygiIzEyNEU3OCIsIiM2RTBFMEEiKQoKdGlkeWNlbnN1czo6Y2Vuc3VzX2FwaV9rZXkoImU3OWYzNzA2YjZkNjEyNDk5NjhjNmNlODg3OTRmNmY1NTZlNWJmM2QiLCBvdmVyd3JpdGUgPSBUUlVFKQpgYGAKClRoZSBkYXRhIHdlIHVzZSBoZXJlIGlzIG9idGFpbmVkIGZyb20gW05ZQyBPcGVuRGF0YV0oaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3RyaXBkYXRhL2luZGV4Lmh0bWwpIHRoYXQgY29udGFpbnMgdGhlIGxhdGVzdCByZWNvcmQgb2YgQ2l0aSBCaWtlIHVzYWdlIG9uIGEgbW9udGhseSBiYXNlLiBOb3RlIHRoYXQgd2hpbGUgSmVyc2V5IENpdHkgaXMgbG9jYXRlZCBpbiBOZXcgSmVyc2V5LCBDaXRpIEJpa2UgaXMgbWFpbmx5IG9wZXJhdGVkIGluIE5ZQyBhbmQgdGhlcmVmb3JlIGl0cyBkYXRhc2V0IGlzIGFsc28gb3BlcmF0ZWQgYnkgTllDLiBXZSBzZWxlY3RlZCBhbGwgYmlrZXNoYXJlIGRhdGEgaW4gSmVyc2V5IENpdHkgZnJvbSBBdWcgMSAyMDIzIHRvIEF1ZyAzMSAyMDIzIGFzIG91ciB0ZW1wb3JhbCBmcmFtZS4gVGhlIGRhdGFzZXQgbWFpbmx5IGluY2x1ZGVzIGxvY2F0aW9ucyBhbmQgbmFtZXMgb2Ygc3RhdGlvbnMgb2Ygd2hpY2ggdGhlIHVzZXIgc3RhcnRlZCB0aGUgcmlkZSBhcyB3ZWxsIGFzIGVuZCB0aGUgcmlkZS4gSW4gYWRkaXRpb24sIHdlIGFsc28gZG93bmxvYWRlZCB0aGUgY2Vuc3VzIHRyYWN0IGdlb21ldHJpZXMgb2YgdGhlIGVudGlyZSBIdWRzb24gQ291bnR5IHZpYSB0aWR5Y2Vuc3VzLCB3aGljaCBpbmNsdWRlcyBKZXJzZXkgQ2l0eS4gCgpgYGB7ciBvYnRhaW4gY2Vuc3VzIGdlb20gYW5kIGJpa2UgZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZScsIGNhY2hlPVRSVUV9CgpueWNiaWtlIDwtIHJlYWQuY3N2KHVybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1OSi1CaWtlU2hhcmUvbWFpbi9KQy0yMDIzMDgtY2l0aWJpa2UtdHJpcGRhdGEuY3N2IikpCgpuakNlbnN1cyA8LSBnZXRfYWNzKGdlb2dyYXBoeSA9ICJ0cmFjdCIsIAogICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMSIsICJCMTkwMTNfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAyIiwgIkIwODAxM18wMDEiLAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MDEyXzAwMSIsICJCMDgzMDFfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgzMDFfMDEwIiwgIkIwMTAwMl8wMDEiKSwgCiAgICAgICAgICB5ZWFyID0gMjAyMSwgCiAgICAgICAgICBzdGF0ZSA9ICJOSiIsIAogICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFLCAKICAgICAgICAgIGNvdW50eT1jKCJIdWRzb24iKSwKICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwKICAgICAgICAgTWVkX0luYyA9IEIxOTAxM18wMDFFLAogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLAogICAgICAgICBUcmF2ZWxfVGltZSA9IEIwODAxM18wMDFFLAogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLAogICAgICAgICBUb3RhbF9QdWJsaWNfVHJhbnMgPSBCMDgzMDFfMDEwRSkgJT4lCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwKICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIE1lZF9BZ2UsCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsCiAgICAgICAgIE1lYW5fQ29tbXV0ZV9UaW1lID0gVHJhdmVsX1RpbWUgLyBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkKCmpjVHJhY3RzIDwtIG5qQ2Vuc3VzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkaXN0aW5jdChHRU9JRCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lCiAgc2VsZWN0KEdFT0lELCBnZW9tZXRyeSkgJT4lIAogIHN0X3NmKCkKYGBgCgpTaW5jZSBvdXIgcmF3IGJpa2VzaGFyZSBkYXRhIGlzIGEgbm9uLXNwYXRpYWwgbGF5ZXIsIGRlc3BpdGUgY29udGFpbmluZyBzcGF0aWFsIGF0dHJpYnV0ZXMsIG91ciBuZXh0IHN0ZXAgaXMgdG8gbWFrZSBpdCBzcGF0aWFsIGJ5IGpvaW5pbmcgaXQgdG8gdGhlIGNlbnN1cyBnZW9tZXRyeS4gV2UgYXJlIG9ubHkgaW50ZXJlc3RlZCBpbiB0aGUgc3RhcnQgbG9jYXRpb24gb2YgZWFjaCByaWRlIGJlY2F1c2Ugd2Ugd2FudCB0byBwcmVkaWN0IHRoZSBjYXBhY2l0eSBvZiBlYWNoIHN0YXRpb24gd2hlbiB0aGUgdXNlciBuZWVkIGEgYmlrZSB0byBzdGFydCB0aGUgcmlkZS4gVGhpcyByZXF1aXJlcyB1cyB0byBmaWx0ZXIgb3V0IHJvd3Mgd2l0aCBtaXNzaW5nIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUuIFdlIGFsc28gZm91bmQgdGhhdCBzb21lIHN0YXRpb25zIGhhdmUgZXJyb25lb3VzIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgY29kaW5nIHRoYXQgbGVhZCB0byB0aGVzZSBwb2ludHMgYmVpbmcgbG9jYXRlZCBpbiB0aGUgSHVkc29uIFJpdmVyLiBUaGVzZSBwb2ludHMgYWxzbyBuZWVkIHRvIGJlIHJlbW92ZWQuIAoKYGBge3IgYWRkIGNlbnN1cyB0cmFjdH0KCmJpa2VfY2Vuc3VzIDwtIHN0X2pvaW4obnljYmlrZSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoaXMubmEoc3RhcnRfbGF0KSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoc3RhcnRfbG5nKSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKGVuZF9sbmcpID09IEZBTFNFKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIHN0X2FzX3NmKC4sIGNvb3JkcyA9IGMoInN0YXJ0X2xuZyIsICJzdGFydF9sYXQiKSwgY3JzID0gNDMyNiksCiAgICAgICAgICAgICAgICAgICAgICAgamNUcmFjdHMgJT4lICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLAogICAgICAgICAgICAgICAgICAgICAgIGpvaW49c3RfaW50ZXJzZWN0cywgbGVmdCA9IFRSVUUpICU+JQogIHJlbmFtZShPcmlnaW4uVHJhY3QgPSBHRU9JRCkgJT4lCiAgbXV0YXRlKHN0YXJ0X2xuZyA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgc3RhcnRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSU+JQogIGZpbHRlcighKHJpZGVfaWQgJWluJSBjKCJCRUM5Q0M0RDEwMDdDNzUzIiwgIjg2MDg3RjQzMTc4NUVERTciLCAiQTFBQUI5MjYzMTQzOTg4MyIsICI3MTkzOERBMDg1MjQyRENDIiwgIjQyMDNDNzc2NjhDNDdDNzUiLCAiQzkyNDEwQ0EyQUVGMzk0NyIsICI2RTAyQ0JGMzZDOTBGMjQ0IiwgIjAyNTk4ODUyNTNGRDIzOEUiLCAiQjM5MTM2QjM3QUZCNUZFOSIsICIxRUM5Mzc2RDE1NTlDNTFDIiwgIkNEREYxNkQzQTM3QkUzNzAiKSkpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkKICAKYGBgCgpUaGUgZm9sbG93aW5nIHZpc3VhbGl6YXRpb24gc2hvd3MgYWxsIHRoZSBzdGVwcyB3ZSBkaWQgYWJvdmUuIAoKYGBge3IgZGlzdHJpYnV0aW9uIG9mIHN0YXRpb25zfQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsPSJ3aGl0ZSIpKwogIGdlb21fcG9pbnQoZGF0YSA9IGJpa2VfY2Vuc3VzLCBhZXMoeD1zdGFydF9sbmcsIHkgPSBzdGFydF9sYXQpLCAKICAgICAgICAgICAgY29sb3IgPSAiIzZFMEUwQSIsIGFscGhhID0gMSwgc2l6ZSA9IDAuMykgKyAKICB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsKICBsYWJzKHRpdGxlID0gIkxvY2F0aW9uIG9mIFJpZGVzIFN0YXJ0aW5nIFBvaW50cyBpbiBKZXJzZXkgQ2l0eSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKCmBgYAoKV2Ugd2lsbCBwZXJmb3JtIHNvbWUgYmFzaWMgZmVhdHVyZSBlbmdpbmVlcmluZyB1c2luZyBSJ3MgcG93ZXJmdWwgYGx1YnJpZGF0ZWAgcGFja2FnZSB0byBleHRyYWN0IHNvbWUgdGVtcG9yYWwgaW5mb3JtYXRpb24gZm9yIGVhY2ggcmlkZS4gVGhpcyBpbmNsdWRlcyBjYWxjdWxhdGluZyB0aGUgNjBtaW4gYW5kIDE1bWluIGludGVydmFsIGZvciB0aGUgc3RhcnRpbmcgdGltZSBvZiBlYWNoIHJpZGUsIGFuZCBjYXRlZ29yaXppbmcgdGhlc2UgaW50ZXJ2YWxzIGludG8gYnkgdGhlIHRpbWUgb2YgZGF5IGludG8gIk92ZXJuaWdodCIsICJBTSBSdXNoIiwgIk1pZC1EYXkiLCBhbmQgIlBNIFJ1c2giIGFuZCBieSBkYXkgb2Ygd2VlayBpbnRvIHdlZWtkYXkgYW5kIHdlZWtlbmQuIAoKYGBge3IgdGVtcG9yYWwgZmVhdHVyZSBlbmdpbmVlcmluZ30KCmJpa2VfY2Vuc3VzIDwtIGJpa2VfY2Vuc3VzICU+JQogIG11dGF0ZShpbnRlcnZhbDYwID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0ZWRfYXQpLCB1bml0ID0gImhvdXIiKSwKICAgICAgICAgaW50ZXJ2YWwxNSA9IGZsb29yX2RhdGUoeW1kX2htcyhzdGFydGVkX2F0KSwgdW5pdCA9ICIxNSBtaW5zIiksCiAgICAgICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lIAogIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSkgJT4lIAogIG11dGF0ZShob3VyID0gaG91cihzdGFydGVkX2F0KSwKICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpKQpgYGAKCkludGVyZXN0ZWQgaW4gc2VlaW5nIHRoZSBjb21tdXRpbmcgcGF0dGVybnMgdG8gZ2V0IGEgc2Vuc2Ugb2YgcmlkZXIncyBkYWlseSBiZWhhdmlvciwgd2UgY3JlYXRlZCBsaW5lIHN0cmluZ3MgdGhhdCBjb25uZWN0IGJldHdlZW4gdGhlIHN0YXJ0IGFuZCBlbmQgbG9jYXRpb25zIG9mIGVhY2ggcmlkZS4gRm9yIHRoZSBzaW1wbGljaXR5IG9mIHZpc3VhbGl6YXRpb24sIHdlIG9ubHkgdmlzdWFsaXplIDIwMCByaWRlcy4gV2UgbWF5IHNlZSB0aGF0IG1vc3Qgb2YgdGhlIHJpZGVzIG9jY3VyIHdpdGhpbiBKZXJzZXkgQ2l0eSwgYnV0IHRoZXJlIGFyZSBhbHNvIHBlb3BsZSB3aG8gY29tbXV0ZSB1c2luZyBDaXRpIEJpa2UgZnJvbSBKZXJzZXkgQ2l0eSB0byBNYW5oYXR0YW4uICAKCmBgYHtyIG9yaWdpbi1kZXNpbnRhdGlvbiBtYXRyaXgsIHdhcm5pbmc9RkFMU0V9CgpsaW5lcyA8LSBzdF9zZmMoCiAgbGFwcGx5KDE6bnJvdyhiaWtlX2NlbnN1cyksIGZ1bmN0aW9uKGkpIHsKICAgIHN0X2xpbmVzdHJpbmcobWF0cml4KGMoYmlrZV9jZW5zdXNbaSwgInN0YXJ0X2xuZyJdLCBiaWtlX2NlbnN1c1tpLCAic3RhcnRfbGF0Il0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaWtlX2NlbnN1c1tpLCAiZW5kX2xuZyJdLCBiaWtlX2NlbnN1c1tpLCAiZW5kX2xhdCJdKSwgbmNvbCA9IDIsIGJ5cm93ID0gVFJVRSkpCiAgfSkpCgpsaW5lc19zZiA8LSBzdF9zZihnZW9tZXRyeSA9IGxpbmVzKQpsaW5lc19zZiA8LSBzdF9zZXRfY3JzKGxpbmVzX3NmLCA0MzI2KQoKUm91dGUgPC0gbGluZXNfc2YgJT4lIGhlYWQoMjAwKQptYXB2aWV3KFJvdXRlLCB6Y29sID0gTlVMTCwgY29sb3IgPSAiIzZFMEUwQSIsIGxpbmV3aWR0aCA9IDAuMDAxLCBhbHBoYSA9IDAuMykgIyBvbmx5IHNob3cgMjAwCmBgYAoKCiMgRXhwbG9yYXRvcnkgQW5hbHlzaXMKCkluIHRoaXMgc2VjdGlvbiwgd2UgZGVsdmUgaW50byBzb21lIGZ1cnRoZXIgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb2Ygb3VyIGRhdGEgc2V0LiAKCiMjIFRlbXBvcmFsIEFuYWx5c2lzCgpTdGFydGluZyB3aXRoIHRlbXBvcmFsIGFuYWx5c2lzLCB3ZSBmaXJzdCB2aXN1YWxpemVkIHRoZSBiaWtlc2hhcmUgdHJpcHMgcGVyIGhvdXIgaW4gSmVyc2V5IENpdHkgaW4gQXVndXN0IDIwMjMuIEl0IGlzIGNsZWFyIHRoYXQgdGhlcmUncyBzb21lIGZvcm0gb2YgY2lyY3VsYXJpdHkgaW4gdGhhdCB0aGVyZSBhcmUgYm90aCBwZWFrIGhvdXJzIGFuZCBub24tcGVhayBob3VycyBkdXJpbmcgYSBzaW5nbGUgZGF5LiBBbHNvLCB3ZSBtYXkgc2VlIHRoYXQgZml2ZSBjb25zZWN1dGl2ZSBoaWdoZXIgcGVha3MgYXJlIHVzdWFsbHkgZm9sbG93ZWQgYnkgdHdvIGxlc3MgaGlnaGVyIHBlYWtzLiBUaGVzZSBjb3JyZXNwb25kIHRvIGhpZ2ggYmlrZXNoYXJlIG5lZWQgZHVyaW5nIHJ1c2ggaG91cnMgYW5kIGxvdyBiaWtlc2hhcmUgbmVlZHMgb3Zlcm5pZ2h0LCBpbiBlYXJseSBtb3JuaW5ncywgYW5kIG92ZXIgdGhlIHdlZWtlbmQKCmBgYHtyIGJpa2VzaGFyZSB2aXoxfQpnZ3Bsb3QoYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JQogICAgICAgICB0YWxseSgpKSsKICBnZW9tX2xpbmUoYWVzKHggPSBpbnRlcnZhbDYwLCB5ID0gbiksIGNvbG9yPSAiIzZFMEUwQSIpKwogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgUGVyIEhvdXIgaW4gSmVyc2V5IENpdHksIEF1ZyAyMDIzIiwKICAgICAgIHg9IkRhdGUiLCAKICAgICAgIHk9Ik51bWJlciBvZiB0cmlwcyIpICsgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCiAgCmBgYApUaGVuLCB3ZSB2aXN1YWxpemVkIHRoZSBkaXN0cmlidXRpb24gb2YgdHJpcHMgYnkgc3RhdGlvbnMgYXQgZWFjaCBob3VyIGluIEF1Z3VzdC4gV2UgbWF5IHNlZSB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgcmlkZXMgaXMgaGlnaGx5IHJpZ2h0IHNrZXdlZCwgd2l0aCBtb3N0IHN0YXRpb25zIGhhdmluZyB6ZXJvIG9yIGEgZmV3IG51bWJlciBvZiB0cmlwcyBlYWNoIGhvdXIgYW5kIGEgY291cGxlIG9mIHN0YXRpb25zIGhhdmluZyBtdWNoIG1vcmUgcmlkZXMsIHVwIHRvIDQwIG9yIGV2ZW4gNTAgcmlkZXMgYXQgYSBwYXJ0aWN1bGFyIGhvdXIgb24gYSBwYXJ0aWN1bGFyIGRheSBpbiBBdWd1c3QgMjAyMy4gCgpgYGB7ciBiaWtlc2hhcmUgdml6M30KCmdncGxvdChiaWtlX2NlbnN1cyAlPiUKICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUKICAgICAgICAgdGFsbHkoKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG4pLCBiaW53aWR0aCA9IDUsIGZpbGw9IiM2RTBFMEEiKSsKICBsYWJzKHRpdGxlPSJCaWtlIFNoYXJlIFRyaXBzIFBlciBIb3VyIGJ5IFN0YXRpb24gaW4gSmVyc2V5IENpdHksIEF1ZyAyMDIzIiwKICAgICAgIHg9IlRyaXAgQ291bnRzIiwgCiAgICAgICB5PSJOdW1iZXIgb2YgU3RhdGlvbnMiKSArIAogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKV2UgY29udGludWUgdG8gdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2YgbWVhbiBudW1iZXIgb2YgdHJpcHMgYXQgZWFjaCBzdGF0aW9uIGR1cmluZyBkaWZmZXJlbnQgdGltZXMgb2YgdGhlIGRheS4gV2UgcmVjb2duaXplIHRoYXQgZHVyaW5nIFBNIHJ1c2gsIHRoZSBhdmVyYWdlIG51bWJlciBvZiB0cmlwcyBoYXMgYmVlbiB0aGUgaGlnaGVzdCB0aHJvdWdob3V0LCB3aXRoIGEgY291cGxlIG9mIHN0YXRpb25zJyBhdmVyYWdlIHRyaXAgZXhjZWVkIDEwLCBpbXBseWluZyBhIHJlbGF0aXZlbHkgaGlnaCBkZW1hbmQgZm9yIGEgc2luZ2xlIHN0YXRpb24uIFRoaXMgaXMgZm9sbG93ZWQgYnkgcmlkZSBkZW1hbmQgaW4gbWlkIGRheSwgd2hlbiBzb21lIHN0YXRpb25zIGNhbiBoYXZlIG1vcmUgdGhhbiA1IHJpZGVzLiBJdCBpcyBhbHNvIHN1cnByaXNpbmcgdG8gbm90aWNlIHRoYXQgdGhlIGRlbWFuZCBpcyBwcmV0dHkgaGlnaCBvdmVybmlnaHQsIHdpdGggYSBmZXcgc3RhdGlvbnMgaGF2aW5nIG1vcmUgdGhhbiA1IHJpZGVzLiAKCmBgYHtyIGJpa2VzaGFyZSB2aXoyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9uYW1lLCB0aW1lX29mX2RheSkgJT4lCiAgICAgICAgIHRhbGx5KCklPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX25hbWUsIHRpbWVfb2ZfZGF5KSU+JQogIHN1bW1hcml6ZShtZWFuX3RyaXBzID0gbWVhbihuKSklPiUKICBnZ3Bsb3QoKSsKICBnZW9tX2hpc3RvZ3JhbShhZXMobWVhbl90cmlwcywgZmlsbD10aW1lX29mX2RheSksIGJpbndpZHRoID0gMSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTQsIG5hbWU9IlRpbWUgb2YgRGF5IikgKyAKICBsYWJzKHRpdGxlPSJNZWFuIE51bWJlciBvZiBIb3VybHkgVHJpcHMgUGVyIFN0YXRpb24iLAogICAgICAgeD0iTnVtYmVyIG9mIHRyaXBzIiwgCiAgICAgICB5PSJGcmVxdWVuY3kiKSsKICBmYWNldF93cmFwKH50aW1lX29mX2RheSkgKwp0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpBZnRlciB0aGF0LCB3ZSB2aXN1YWxpemVkIHRoZSB0b3RhbCBudW1iZXIgb2YgdHJpcHMgaW4gQXVndXN0IHNlcGFyYXRlZCBieSBkYXkgb2YgdGhlIHdlZWsuIFRoZSB0d28gbWFpbiBwYXR0ZXJucyBoZXJlIGFyZSB0aGF0IGZpcnN0bHksIHRyaXAgbnVtYmVycyB2YXJ5IGJ5IHRpbWUgb2YgdGhlIGRheS4gTG93ZXN0IHRyaXAgY291bnRzIG9jY3VyIGluIGVhcmx5IG1vcm5pbmcgd2hpbGUgaGlnaGVzdCB0cmlwIGNvdW50IG9jY3VyIGluIGxhdGUgYWZ0ZXJub29uLiBTZWNvbmRseSwgVHVlc2RheSwgV2VkbmVzZGF5LCBhbmQgVGh1cnNkYXkgdGVuZCB0byBvYnNlcnZlIGhpZ2hlciBudW1iZXIgb2YgdHJpcHMuIAoKYGBge3IgYmlrZXNoYXJlIHZpejR9CgpnZ3Bsb3QoYmlrZV9jZW5zdXMgJT4lIG11dGF0ZShob3VyID0gaG91cihzdGFydGVkX2F0KSkpKwogICAgIGdlb21fZnJlcXBvbHkoYWVzKGhvdXIsIGNvbG9yID0gZG90dyksIGJpbndpZHRoID0gMywgbHdkID0gMC44KSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiIzEyNEU3OCIsICIjODE5RkExIiwgIiNGMEYwQzkiLCAiI0YyQkIwNSIsICIjRTU4NTA3IiwgIiNENzRFMDkiLCAiIzZFMEUwQSIpLCBuYW1lID0gIkRheSIpICsgCiAgbGFicyh0aXRsZT0iQmlrZSBTaGFyZSBUcmlwcyBpbiBKZXJzZXkgQ2l0eSBieSBEYXkgb2YgdGhlIFdlZWssIEF1ZyAyMDIzIiwKICAgICAgIHg9IkhvdXIiLCAKICAgICAgIHk9IlRyaXAgQ291bnRzIikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgClNhbWUgdHlwZSBvZiB2aXN1YWxpemUgYnV0IHRoaXMgdGltZSBzZXBhcmF0ZWQgYmV0d2VlbiB3ZWVrZW5kIGFuZCB3ZWVrZGF5cy4gSXQgaXMgdmVyeSBvYnZpb3VzIHRoYXQgd2Ugc2VlIG1vcmUgdHJpcHMgZHVyaW5nIHdlZWtkYXlzIGFuZCBsZXNzIHRyaXBzIGR1cmluZyB3ZWVrZW5kcy4gQWxzbywgZHVyaW5nIHdlZWtlbmRzLCBudW1iZXIgb2YgdHJpcHMgc3RhcnQgdG8gaW5jcmVhc2UgbXVjaCBsYXRlciB0aGFuIGR1cmluZyB3ZWVrZGF5cywgc3VnZ2VzdGluZyB0aGUgbWlzc2luZyBvZiBtb3JuaW5nIHJ1c2ggaG91ciB3aWxsIHNpZ25pZmljYW50bHkgaW1wYWN0IHBlb3BsZSdzIHVzZSBvZiBiaWtlc2hhcmUgc2VydmljZXMuIAoKYGBge3IgYmlrZXNoYXJlIHZpeiA1fQoKZ2dwbG90KGJpa2VfY2Vuc3VzKSsKICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IHdlZWtlbmQpLCBiaW53aWR0aCA9IDMsIGx3ZCA9IDEuMikrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1wYWxldHRlMikgKyAKICBsYWJzKGNvbG9yID0gIlRpbWUgb2YgV2VlayIpICsKICBsYWJzKHRpdGxlPSJCaWtlIFNoYXJlIFRyaXBzIGluIEplcnNleSBDaXR5IC0gV2Vla2VuZCB2cyBXZWVrZGF5LCBBdWcgMjAyMyIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYApCdWlsZGluZyBvbnRvIHRoYXQsIHdlIGdyb3VwIHRoaXMgZGF0YXNldCBieSBzdGF0aW9uIGFuZCB0aW1lIG9mIGRheSB0byB2aXN1YWxpemUgdGhlIHRvdGFsIG51bWJlciBvZiB0cmlwcyBhdCBlYWNoIHN0YXRpb24gZHVyaW5nIGEgc3BlY2lmaWMgcGVyaW9kIG9mIHRpbWUgb24gd2Vla2RheSBvciB3ZWVrZW5kLiBUbyBzaW1wbGlmeSBvdXIgdmlzdWFsaXphdGlvbiwgd2Ugc2VsZWN0ZWQgdGhlIHRvcCA1MCBvY2N1cnJlbmNlcyBpbiBvdXIgZGF0YXNldC4gTm90ZSB0aGF0IGhlcmUsIGVhY2ggYmFyIHJlcHJlc2VudCBhbiBpbnN0YW5jZSBvZiBzdGF0aW9uLXRpbWUgb2YgZGF5LXdlZWtkYXkvd2Vla2VuZCBjb21iaW5hdGlvbi4gV2UgYXJlIGFibGUgdG8gc2VlIHRoYXQgUE0gcnVzaCBob3VyIGluZGVlZCBzZWVzIHRoYXQgZ3JlYXRlc3QgYmlrZXNoYXJlIGRlbWFuZCBmb3IgYSBjb3VwbGUgb2Ygc3RhdGlvbnMgaW4gcGFydGljdWxhci4gV2Vla2VuZCByaWRlcyBvbmx5IGFwcGVhciB0aHJlZSB0aW1lcyBpbiB0aGUgdG9wIDUwIGxpc3QuIFpvb21pbmcgaW50byB0aGUgZGV0YWlsIHJldmVhbHMgdG8gdXMgdGhhdCAqKkdyb3ZlIFN0IFBBVEgqKiBzdGF0aW9uIG9ic2VydmVzIGEgdG90YWwgb2YgMTExNyByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiBhIHdlZWtkYXksIDgwMSByaWRlcyBvdmVybmlnaHQgb24gYSB3ZWVrZGF5IGluIEF1Z3VzdCAyMDIzLiAqKlNvdXRoIFdhdGVyZnJvbnQgV2Fsa3dheSAtIFNpbmF0cmEgRHIgJiAxIFN0Kiogc3RhdGlvbiBvYnNlcnZlcyAzMTkgcmlkZXMgb3Zlcm5pZ2h0IG9uIGEgd2Vla2VuZCBhbmQgNzMxIHJpZGVzIGR1cmluZyBQTSBydXNoIG9uIGEgd2Vla2RheSBpbiBBdWd1c3QgMjAyMy4gCgpgYGB7ciBiaWtlc2hhcmUgdml6IDcsIGZpZy53aWR0aD0xMH0KCnRvX3Bsb3QgPC0gYmlrZV9jZW5zdXMlPiUgIyBvbmx5IHNob3cgMTAwIHN0YXRpb25zCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbGF0LCBzdGFydF9sbmcsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5LCBzdGFydF9zdGF0aW9uX25hbWUpICU+JQogIHRhbGx5KCkgJT4lIAogIGFycmFuZ2UoLW4pICU+JSAKICBoZWFkKDUwKSAKdG9fcGxvdCRJRCA8LSBzZXFfYWxvbmcodG9fcGxvdCRzdGFydF9zdGF0aW9uX2lkKQoKdG9fcGxvdCAlPiUgCiAgYXJyYW5nZSgtbikgJT4lCiAgaGVhZCg1MCkgJT4lIAogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoSUQsIC1uKSwgbiwgZmlsbCA9IHRpbWVfb2ZfZGF5LCBjb2xvciA9IHdlZWtlbmQpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTQsIG5hbWU9IlRpbWUgb2YgRGF5IikgKyAKICBndWlkZXMoY29sb3I9Im5vbmUiKSArIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbj0ic3RhY2siKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoInRyYW5zcGFyZW50IiwgImJsYWNrIikpICsKICBsYWJzKHRpdGxlPSJUb3AgNTAgT2NjdXJlbmNlcyBvZiBSaWRlcyBCeSBUaW1lIikrCiAgeWxhYigiTnVtYmVyIG9mIFJpZGVzIikgKwogIHhsYWIoIiIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKYGBgCgoKIyMgU3BhdGlhbCBBbmFseXNpcwoKYGBge3IgYmlrZXNoYXJlIHZpeiA2fQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsID0gIndoaXRlIikrCiAgZ2VvbV9wb2ludChkYXRhID0gdG9fcGxvdCwKICAgICAgICAgICAgIGFlcyh4PXN0YXJ0X2xuZywgeSA9IHN0YXJ0X2xhdCwgc2l6ZT1uKSwgY29sb3IgPSBhbHBoYSgiIzZFMEUwQSIsIDAuNykpICsgCiAgeWxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCkpKwogIHhsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZyksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sbmcpKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMC41LCA4KSkgKyAKICBsYWJzKHNpemUgPSAiTnVtYmVyIG9mIFJpZGVzIikgKwogIGZhY2V0X2dyaWQod2Vla2VuZCB+IHRpbWVfb2ZfZGF5KSArIAogICAgbGFicyh0aXRsZT0iTG9jYXRpb25zIG9mIFN0YXRpb25zIHdpdGggR3JlYXRlc3QgQmlrZXNoYXJlIERlbWFuZCBieSBUaW1lIikrIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCgoKYGBge3Igc3BhdGlhbCBhdXRvIGNvciwgd2FybmluZz1GQUxTRX0KCm1vcmFuX2RhdGEgPC0gYmlrZV9jZW5zdXMgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbGF0LCBzdGFydF9sbmcsIHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lCiAgdGFsbHkoKSAlPiUgCiAgYXJyYW5nZSgtbikgJT4lIAogIGhlYWQoODApICU+JSAKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sbmciLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpCgoKY29vcmRzIDwtICBzdF9jb29yZGluYXRlcyhtb3Jhbl9kYXRhKSAKbmVpZ2hib3JMaXN0IDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3JkcywgNSkpCnNwYXRpYWxXZWlnaHRzIDwtIG5iMmxpc3R3KG5laWdoYm9yTGlzdCwgc3R5bGU9IlciKQoKbW9yYW5UZXN0IDwtIG1vcmFuLm1jKG1vcmFuX2RhdGEkbiwgCiAgICAgICAgICAgICAgICAgICAgICBzcGF0aWFsV2VpZ2h0cywgbnNpbSA9IDk5OSkKCmdncGxvdChhcy5kYXRhLmZyYW1lKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSwgYWVzKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICIjMTI0RTc4IikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhblRlc3Qkc3RhdGlzdGljKSwgY29sb3VyID0gIiM2RTBFMEEiICxsd2Q9MSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKC0wLjUsIDAuNSkpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmVkIGFuZCBQZXJtdXRlZCBNb3JhbidzIEkgZm9yIFN0YXRpb25zIHdpdGggTW9zdCBSaWRlcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJPYnNlcnZlZCBNb3JhbidzIEkgaW4gUmVkIiwKICAgICAgIHggPSAiTW9yYW4ncyBJIiwKICAgICAgIHkgPSAiQ291bnQiKSArCiAgdGhlbWVfbGlnaHQoKSArICAgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSkKYGBgCgoKYGBge3Igc3BhdGlhbCBjb3Igdml6LCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMX0KCiAgZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGpjVHJhY3RzLCBmaWxsID0gIndoaXRlIikgKwogIGdlb21fc2YoZGF0YSA9IG1vcmFuX2RhdGEsCiAgICAgICAgICBhZXMoc2l6ZSA9IG4sIGNvbG9yID0gbikpICsgCiAgeWxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCkpKwogIHhsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZyksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sbmcpKSArIAogIHNjYWxlX3NpemUoCiAgICByYW5nZSA9IGMoMSwgNiksCiAgICBndWlkZSA9IGd1aWRlX2xlZ2VuZCgKICAgICAgZGlyZWN0aW9uID0gImhvcml6b250YWwiLAogICAgICBucm93ID0gMSwKICAgICAgbGFiZWwucG9zaXRpb24gPSAiYm90dG9tIikpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3VycyA9IGhjbC5jb2xvcnMoNSwgIlJkQnUiLHJldiA9IFRSVUUsIGFscGhhID0gMC45KSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKGRpcmVjdGlvbiA9ICJob3Jpem9udGFsIiwgbnJvdyA9IDEpKSArCiAgbGFicyh0aXRsZSA9ICJTdGF0aW9ucyB3aXRoIE1vc3QgUmlkZXMiLAogICAgICAgc3VidGl0bGUgPSAgIkRvdCBzaXplIHByb3BvcnRpb25hbCB0byByaWRlcyIpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCgpgYGAKCmBgYHtyIGFuaW1hdGUgbWFwIHRlbXBvcmFsIHZpeiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgZmlnLnNob3c9J2FuaW1hdGUnfQoKd2VlazMzIDwtCiAgZmlsdGVyKGJpa2VfY2Vuc3VzICwgd2VlayA9PSAzMyAmIGRvdHcgPT0gIk1vbiIpCgp3ZWVrMzMucGFuZWwgPC0KICBleHBhbmQuZ3JpZCgKICAgIGludGVydmFsMTUgPSB1bmlxdWUod2VlazMzJGludGVydmFsMTUpLAogICAgT3JpZ2luLlRyYWN0ID0gdW5pcXVlKGJpa2VfY2Vuc3VzJE9yaWdpbi5UcmFjdCkpCgpiaWtlLmFuaW1hdGlvbi5kYXRhIDwtCiAgbXV0YXRlKHdlZWszMywgVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgICByaWdodF9qb2luKHdlZWszMy5wYW5lbCkgJT4lIAogICAgZ3JvdXBfYnkoaW50ZXJ2YWwxNSwgT3JpZ2luLlRyYWN0KSAlPiUKICAgIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnRlciwgbmEucm09VCkpICU+JSAKICAgIHVuZ3JvdXAoKSAlPiUgCiAgICBsZWZ0X2pvaW4oamNUcmFjdHMsIGJ5PWMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKSkgJT4lCiAgICBzdF9zZigpICU+JQogICAgbXV0YXRlKFRyaXBzID0gY2FzZV93aGVuKFRyaXBfQ291bnQgPT0gMCB+ICIwIHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMCAmIFRyaXBfQ291bnQgPD0gMyB+ICIxLTMgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAzICYgVHJpcF9Db3VudCA8PSA2IH4gIjQtNiB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDYgJiBUcmlwX0NvdW50IDw9IDEwIH4gIjctMTAgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAxMCB+ICIxMSsgdHJpcHMiKSkgJT4lCiAgICBtdXRhdGUoVHJpcHMgID0gZmN0X3JlbGV2ZWwoVHJpcHMsICIwIHRyaXBzIiwiMS0zIHRyaXBzIiwiNC02IHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjctMTAgdHJpcHMiLCIxMCsgdHJpcHMiKSkKCmJpa2VzaGFyZV9hbmltYXRpb24gPC0KICBnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGRhdGEgPSBiaWtlLmFuaW1hdGlvbi5kYXRhLCBhZXMoZmlsbCA9IFRyaXBzKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsKICAgIGxhYnModGl0bGUgPSAiQmlrZXNoYXJlIEZvciBPbmUgRGF5IGluIEF1ZyAyMDIzIiwKICAgICAgICAgc3VidGl0bGUgPSAiMTUgbWludXRlIGludGVydmFsczoge2N1cnJlbnRfZnJhbWV9IikgKwogICAgdHJhbnNpdGlvbl9tYW51YWwoaW50ZXJ2YWwxNSkgICsgCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCiAgCgojYW5pbWF0ZShiaWtlc2hhcmVfYW5pbWF0aW9uLCBkdXJhdGlvbj00MCwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIoKSkKCmFuaW1fc2F2ZSgiYW5pbWF0aW9uLmdpZiIsIGFuaW1hdGlvbiA9IGJpa2VzaGFyZV9hbmltYXRpb24sIGVuZF9wYXVzZSA9IDQsIGhlaWdodCA9IDE4MDAsIHdpZHRoID0gMzAwMCwgZnBzID0gNiwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIoKSkKCgoKYGBgCgohW2FuaW1hdGVdKC9Vc2Vycy9lbXpob3UvRG9jdW1lbnRzL0dpdEh1Yi9NVVNBNTA4MC1OSi1CaWtlU2hhcmUvYW5pbWF0aW9uLmdpZikKCiMjIFdlYXRoZXIgQW5hbHlzaXMKCmBgYHtyIGRvd25sb2FkIGFuZCB3cmFuZ2xlIHdlYXRoZXIgZGF0YSwgd2FybmluZz1GQUxTRX0KCndlYXRoZXIuUGFuZWwgPC0gCiAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIkVXUiIsIGRhdGVfc3RhcnQgPSAiMjAyMy0wOC0wMSIsIGRhdGVfZW5kID0gIjIwMjMtMDktMDEiKSAlPiUKICBkcGx5cjo6c2VsZWN0KHZhbGlkLCB0bXBmLCBwMDFpLCBza250KSU+JQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQogICAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsMSwxMykpKSAlPiUKICAgIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShUZW1wZXJhdHVyZSA9IG1heCh0bXBmKSwKICAgICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLAogICAgICAgICAgICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkpICU+JQogICAgbXV0YXRlKFRlbXBlcmF0dXJlID0gaWZlbHNlKFRlbXBlcmF0dXJlID09IDAsIDQyLCBUZW1wZXJhdHVyZSkpCgpgYGAKCgpgYGB7ciB2aXN1YWxpemUgd2VhdGhlciBkYXRhLCBmaWcuaGVpZ2h0PTExLCBmaWcud2lkdGg9OCwgd2FybmluZz1GQUxTRX0KCmdyaWQuYXJyYW5nZSgKICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsUHJlY2lwaXRhdGlvbikpICsgZ2VvbV9saW5lKGNvbG9yPSIjNkUwRTBBIikgKyAKICAgIGxhYnModGl0bGU9IlBlcmNpcGl0YXRpb24iLCB4PSJIb3VyIiwgeT0iUGVyZWNpcGl0YXRpb24iKSArIAogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSwgCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFdpbmRfU3BlZWQpKSArIGdlb21fbGluZShjb2xvcj0iI0Q3NEUwOSIpICsgCiAgICBsYWJzKHRpdGxlPSJXaW5kIFNwZWVkIiwgeD0iSG91ciIsIHk9IldpbmQgU3BlZWQiKSArCiAgICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpLCAgCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFRlbXBlcmF0dXJlKSkgKyBnZW9tX2xpbmUoY29sb3I9IiMxMjRFNzgiKSArIAogICAgbGFicyh0aXRsZT0iVGVtcGVyYXR1cmUiLCB4PSJIb3VyIiwgeT0iVGVtcGVyYXR1cmUiKSArdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSwgCiAgdG9wPSJXZWF0aGVyIERhdGEgIikKCmBgYAoKRXZlcnl0aGluZyBiZWxvdyBuZWVkcyByZS1ydW4KCiMgU3BhY2UtVGltZSBQYW5lbAoKYGBge3IgY3JlYXRlIHN0dWR5IHBhbmVsLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKc3R1ZHkucGFuZWwgPC0gCiAgZXhwYW5kLmdyaWQoaW50ZXJ2YWw2MD11bmlxdWUoYmlrZV9jZW5zdXMkaW50ZXJ2YWw2MCksIAogICAgICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSB1bmlxdWUoYmlrZV9jZW5zdXMkc3RhcnRfc3RhdGlvbl9pZCkpICU+JQogIGxlZnRfam9pbiguLCBiaWtlX2NlbnN1cyAlPiUKICAgICAgICAgICAgICBzZWxlY3Qoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfc3RhdGlvbl9uYW1lLCBPcmlnaW4uVHJhY3QsIHN0YXJ0X2xuZywgc3RhcnRfbGF0LCByaWRlYWJsZV90eXBlKSU+JQogICAgICAgICAgICAgIGRpc3RpbmN0KCkgJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCkgJT4lCiAgICAgICAgICAgICAgc2xpY2UoMSkpCmBgYAoKCmBgYHtyIGFkZCBpbmZvIHRvIHN0dWR5IHBhbmVsLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKYmlrZS5wYW5lbCA8LSAKICBiaWtlX2NlbnN1cyAlPiUKICBtdXRhdGUoVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgcmlnaHRfam9pbihzdHVkeS5wYW5lbCkgJT4lIAogIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X3N0YXRpb25fbmFtZSwgT3JpZ2luLlRyYWN0LCBzdGFydF9sbmcsIHN0YXJ0X2xhdCkgJT4lCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudGVyLCBuYS5ybT1UKSkgJT4lCiAgbGVmdF9qb2luKHdlYXRoZXIuUGFuZWwpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIoaXMubmEoc3RhcnRfc3RhdGlvbl9pZCkgPT0gRkFMU0UpICU+JQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSkgJT4lCiAgZmlsdGVyKGlzLm5hKE9yaWdpbi5UcmFjdCkgPT0gRkFMU0UpICU+JSAKICBsZWZ0X2pvaW4oLiwgbmpDZW5zdXMgJT4lCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgICAgICAgICAgIHNlbGVjdCgtZ2VvbWV0cnkpLCBieSA9IGMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKSkKCmBgYAoKIyMgV2VhdGhlciBDb3JyZWxhdGlvbgoKYGBge3IgdmlzdWFsaXplIHRlbXAsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpiaWtlLnBhbmVsICU+JQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JSAKICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IG1lYW4oVHJpcF9Db3VudCksCiAgICAgICAgICAgIFRlbXBlcmF0dXJlID0gZmlyc3QoVGVtcGVyYXR1cmUpKSAlPiUKICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCkpICU+JQogIGdncGxvdChhZXMoVGVtcGVyYXR1cmUsIFRyaXBfQ291bnQpKSArIAogICAgZ2VvbV9wb2ludChjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iVHJpcCBDb3VudCBBcyBhIEZ1Y3Rpb24gb2YgVGVtcGVyYXR1cmUgYnkgV2VlayIsCiAgICAgICAgIHg9IlRlbXBlcmF0dXJlIiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKICAKCmBgYAoKCmBgYHtyIHZpc3VhbGl6ZSB3aW5kLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpiaWtlLnBhbmVsICU+JQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JSAKICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IG1lYW4oVHJpcF9Db3VudCksCiAgICAgICAgICAgIFdpbmQgPSBmaXJzdChXaW5kX1NwZWVkKSkgJT4lCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApKSAlPiUKICBnZ3Bsb3QoYWVzKFdpbmQsIFRyaXBfQ291bnQpKSArIAogICAgZ2VvbV9wb2ludChjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iVHJpcCBDb3VudCBBcyBhIEZ1Y3Rpb24gb2YgV2luZCBieSBXZWVrIiwKICAgICAgICAgeD0iV2luZCBTcGVlZCIsIHk9Ik1lYW4gVHJpcCBDb3VudCIpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpICsgCiAgICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKYGBgCgoKIyMgU2VyaWFsIENvcnJlbGF0aW9uCmBgYHtyIGNhbGN1bGF0ZSBzZXJpYWwgbGFnfQoKYmlrZS5wYW5lbCA8LSAKICBiaWtlLnBhbmVsICU+JSAKICBhcnJhbmdlKHN0YXJ0X3N0YXRpb25faWQsIGludGVydmFsNjApICU+JSAKICBtdXRhdGUobGFnSG91ciA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxKSwKICAgICAgICAgbGFnMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDIpLAogICAgICAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMyksCiAgICAgICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCw0KSwKICAgICAgICAgbGFnMTJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxMiksCiAgICAgICAgIGxhZzFkYXkgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMjQpKSAlPiUKICAgbXV0YXRlKGRheSA9IHlkYXkoaW50ZXJ2YWw2MCkpCgpgYGAKCgpgYGB7ciB2aXN1YWxpemUgc2VyaWFsIGNvcnJlbGF0aW9ucywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmFzLmRhdGEuZnJhbWUoYmlrZS5wYW5lbCkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUgCiAgICBzdW1tYXJpc2VfYXQodmFycyhzdGFydHNfd2l0aCgibGFnIiksICJUcmlwX0NvdW50IiksIG1lYW4sIG5hLnJtID0gVFJVRSkgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtaW50ZXJ2YWw2MCwgLVRyaXBfQ291bnQpICU+JQogIGdncGxvdChhZXMoeCA9IFZhbHVlICwgeSA9IFRyaXBfQ291bnQpKSArCiAgICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogIGZhY2V0X3dyYXAoflZhcmlhYmxlLCBuY29sID0gNikrCiAgbGFicyh4ID0gIlZhcmlhYmxlIiwgeSA9ICJDb3JyZWxhdGlvbiIsIHRpdGxlID0gIkNvcnJlbGF0aW9uIEJldHdlZW4gU2VyaWFsIExhZyBUcmlwcyBhbmQgVHJpcCBDb3VudCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgojIyBTcGF0aWFsIENvcnJlbGF0aW9uCgpgYGB7ciBjYWxjdWxhdGUgc3BhdGlhbCBsYWcsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpsYWdfcmVzdWx0IDwtIGRhdGEuZnJhbWUoKQoKZm9yIChkIGluIDIxMzoyNDMpIHsKICBmb3IgKGggaW4gMDoyMykgewoKc3BhY2VsYWcgPC0gYmlrZS5wYW5lbCAlPiUgCiAgbXV0YXRlKGhvdXIgPSBob3VyKGludGVydmFsNjApKSAlPiUgCiAgZmlsdGVyKGRheSA9PSBkICYgaG91ciA9PSBoKSAlPiUgCiAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygic3RhcnRfbG5nIiwgInN0YXJ0X2xhdCIpLCBjcnMgPSA0MzI2KSAKCgpjb29yZHMudGVzdF9sYWcgPC0gIHN0X2Nvb3JkaW5hdGVzKHNwYWNlbGFnKSAKbmVpZ2hib3JMaXN0LnRlc3RfbGFnIDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3Jkcy50ZXN0X2xhZywgNSkpCnNwYXRpYWxXZWlnaHRzLnRlc3RfbGFnIDwtIG5iMmxpc3R3KG5laWdoYm9yTGlzdC50ZXN0X2xhZywgc3R5bGU9IlciKQoKc3BhY2VsYWcgPC0gc3BhY2VsYWcgJT4lIG11dGF0ZShsYWdUcmlwID0gbGFnLmxpc3R3KHNwYXRpYWxXZWlnaHRzLnRlc3RfbGFnLCBUcmlwX0NvdW50KSkKCmxhZ19yZXN1bHQgPC0gcmJpbmQobGFnX3Jlc3VsdCwgc3BhY2VsYWcpCiAgfQogIAp9CgpiaWtlLnBhbmVsIDwtIGxhZ19yZXN1bHQgJT4lIAogIG11dGF0ZShzdGFydF9sbmcgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAxKSksCiAgICAgICAgIHN0YXJ0X2xhdCA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSkgJT4lIAogc3RfZHJvcF9nZW9tZXRyeSgpIAogIAogIApgYGAKCgpgYGB7ciB2aXN1YWxpemUgc3BhdGlhbCBjb3JyZWxhdGlvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmJpa2UucGFuZWwgJT4lCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gbWVhbihUcmlwX0NvdW50KSwKICAgICAgICAgICAgTGFnX0NvdW50ID0gbWVhbihsYWdUcmlwKSkgJT4lCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApKSAlPiUKICBnZ3Bsb3QoYWVzKExhZ19Db3VudCwgVHJpcF9Db3VudCkpICsgCiAgICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iQ29ycmVsYXRpb24gQmV0d2VlbiBTcGF0aWFsIExhZyBUcmlwcyBhbmQgVHJpcCBDb3VudCIsCiAgICAgICAgIHg9IkxhZyBUcmlwIENvdW50IiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKCiMgT0xTIFJlZ3Jlc3Npb24KCmBgYHtyIGZpdHRpbmcgcmVncmVzc2lvbn0KCmJpa2UuVHJhaW4gPC0gZmlsdGVyKGJpa2UucGFuZWwsIHdlZWsgPD0gMzMpCmJpa2UuVGVzdCA8LSBmaWx0ZXIoYmlrZS5wYW5lbCwgd2VlayA+IDMzKQoKIyBUaW1lCnJlZzEgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCwgZGF0YT1iaWtlLlRyYWluKQoKIyBTcGFjZQpyZWcyIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbl9uYW1lICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCwgIGRhdGE9YmlrZS5UcmFpbikKCiMgVGltZSBhbmQgU3BhY2UKcmVnMyA8LSAKICBsbShUcmlwX0NvdW50IH4gIHN0YXJ0X3N0YXRpb25fbmFtZSArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBXaW5kX1NwZWVkLCAKICAgICBkYXRhPWJpa2UuVHJhaW4pCgojIFRpbWUgYW5kIFNwYWNlIGFuZCBUaW1lTGFnIGFuZCBTcGFjZUxhZwpyZWc0IDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbl9uYW1lICsgIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBXaW5kX1NwZWVkICsKICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSArIGxhZ1RyaXAsIAogICAgIGRhdGE9YmlrZS5UcmFpbikKYGBgCgojIyBNYWtpbmcgUHJlZGljdGlvbnMKCmBgYHtyIG5lc3QgZGF0YSwgd2FybmluZz1GQUxTRX0KCmJpa2UuVGVzdC53ZWVrTmVzdCA8LSAKICBiaWtlLlRlc3QgJT4lCiAgbmVzdCgtd2VlaykgCgpgYGAKCgpgYGB7ciBwcmVkaWN0IGZ1bmN0aW9ufQoKbW9kZWxfcHJlZCA8LSBmdW5jdGlvbihkYXQsIGZpdCl7CiAgIHByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXQpfQoKYGBgCgoKYGBge3IgbWFraW5nIHByZWRpY3Rpb25zLCB3YXJuaW5nPUZBTFNFfQoKd2Vla19wcmVkaWN0aW9ucyA8LSAKICBiaWtlLlRlc3Qud2Vla05lc3QgJT4lIAogICAgbXV0YXRlKEFUaW1lX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMSwgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBCU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcyLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIENUaW1lX1NwYWNlX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMywgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBEVGltZV9TcGFjZV9GRV9MYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNCwgLmYgPSBtb2RlbF9wcmVkKSkgJT4lIAogICAgZ2F0aGVyKFJlZ3Jlc3Npb24sIFByZWRpY3Rpb24sIC1kYXRhLCAtd2VlaykgJT4lCiAgICBtdXRhdGUoT2JzZXJ2ZWQgPSBtYXAoZGF0YSwgcHVsbCwgVHJpcF9Db3VudCksCiAgICAgICAgICAgQWJzb2x1dGVfRXJyb3IgPSBtYXAyKE9ic2VydmVkLCBQcmVkaWN0aW9uLCAgfiBhYnMoLnggLSAueSkpLAogICAgICAgICAgIE1BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIG1lYW4sIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgc2RfQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBzZCwgbmEucm0gPSBUUlVFKSkKCmBgYAoKIyMgRXJyb3IgQW5hbHlzaXMKCmBgYHtyIHBsb3QgZXJyb3JzfQoKd2Vla19wcmVkaWN0aW9ucyAlPiUKICBkcGx5cjo6c2VsZWN0KHdlZWssIFJlZ3Jlc3Npb24sIE1BRSkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBNQUUsIC1SZWdyZXNzaW9uLCAtd2VlaykgJT4lCiAgZ2dwbG90KGFlcyh3ZWVrLCBNQUUpKSArIAogICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBSZWdyZXNzaW9uKSwgcG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0KSArCiAgICBsYWJzKHRpdGxlID0gIk1lYW4gQWJzb2x1dGUgRXJyb3JzIGJ5IE1vZGVsIFNwZWNpZmljYXRpb24gYW5kIFdlZWsiKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCgpgYGAKCgpgYGB7ciBjb21wYXJpbmcgcHJlZGljdGVkIGFuZCBvYnNlcnZlZCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9Cgp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9pZCkpICU+JQogICAgZHBseXI6OnNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX2lkLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lCiAgICB1bm5lc3QoKSAlPiUKICAgIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1SZWdyZXNzaW9uLCAtaW50ZXJ2YWw2MCwgLXN0YXJ0X3N0YXRpb25faWQpICU+JQogICAgZ3JvdXBfYnkoUmVncmVzc2lvbiwgVmFyaWFibGUsIGludGVydmFsNjApICU+JQogICAgc3VtbWFyaXplKFZhbHVlID0gc3VtKFZhbHVlKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKGludGVydmFsNjAsIFZhbHVlLCBjb2xvdXI9VmFyaWFibGUpKSArIAogICAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1wYWxldHRlMikrCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKwogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBCaWtlIFNoYXJlIFRpbWUgU2VyaWVzIiwgc3VidGl0bGUgPSAiSmVyc2V5IENpdHk6IGEgdGVzdCBzZXQgb2YgMiB3ZWVrcyIsICB4ID0gIkhvdXIiLCB5PSAiU3RhdGlvbiBUcmlwcyIpICsgCiAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgoKYGBge3IgZXJyb3IgYnkgc3RhdGlvbiwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRX0KCndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgCiAgICAgICAgICAgc3RhcnRfbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbGF0KSwgCiAgICAgICAgICAgc3RhcnRfbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xuZykpICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X2xvbmdpdHVkZSwgc3RhcnRfbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUKICAgIHVubmVzdCgpICU+JQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJEVGltZV9TcGFjZV9GRV9MYWdzIikgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbG9uZ2l0dWRlLCBzdGFydF9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQpnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMsICBmaWxsID0gIndoaXRlIikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2xvbmdpdHVkZSwgeSA9IHN0YXJ0X2xhdGl0dWRlLCBjb2xvciA9IE1BRSksIAogICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKwogIHNjYWxlX2NvbG9yX2NvbnRpbnVvdXMobG93ID0gIiMxMjRFNzgiLCBoaWdoID0gIiM2RTBFMEEiLCBuYW1lPSAiTUFFIikrCiB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsKICBsYWJzKHRpdGxlPSJNZWFuIEFicyBFcnJvciwgVGVzdCBTZXQsIE1vZGVsIDQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQoKYGBgCgoKYGBge3IgY29tcGFyaW5nIGVycm9ycyBhY3Jvc3Mgc3BhY2UgYW5kIHRpbWUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPVRSVUV9Cgp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9pZCksIAogICAgICAgICAgIHN0YXJ0X2xhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xhdCksIAogICAgICAgICAgIHN0YXJ0X2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sbmcpLAogICAgICAgICAgIGRvdHcgPSBtYXAoZGF0YSwgcHVsbCwgZG90dykgKSAlPiUKICAgIHNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX2lkLCBzdGFydF9sb25naXR1ZGUsIAogICAgICAgICAgIHN0YXJ0X2xhdGl0dWRlLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbiwKICAgICAgICAgICBkb3R3KSAlPiUKICAgIHVubmVzdCgpICU+JQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJEVGltZV9TcGFjZV9GRV9MYWdzIiklPiUKICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgc3RhcnRfbG9uZ2l0dWRlLCBzdGFydF9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQogIGdncGxvdCguKSsKICBnZW9tX3NmKGRhdGEgPSBqY1RyYWN0cywgY29sb3IgPSAiZ3JleSIsIGZpbGwgPSAidHJhbnNwYXJlbnQiKSsKICBnZW9tX3BvaW50KGFlcyh4ID0gc3RhcnRfbG9uZ2l0dWRlLCB5ID0gc3RhcnRfbGF0aXR1ZGUsIGNvbG9yID0gTUFFKSwgCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50Iiwgc2l6ZSA9IDAuNSkrCiAgc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAiIzEyNEU3OCIsIGhpZ2ggPSAiIzZFMEUwQSIsIG5hbWU9ICJNQUUiKSsKIHlsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sYXQpKSsKICB4bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sbmcpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSkgKwogIGZhY2V0X2dyaWQod2Vla2VuZH50aW1lX29mX2RheSkrCiAgbGFicyh0aXRsZT0iTWVhbiBBYnNvbHV0ZSBFcnJvcnMgYnkgVGltZSwgVGVzdCBTZXQsIE1vZGVsIDQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCiMjIENyb3NzLVZhbGlkYXRpb24KCmBgYHtyIGNyb3NzIHZhbGlkYXRpb259CgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMDApCgpyZWcuY3YgPC0gdHJhaW4oVHJpcF9Db3VudCB+IHN0YXJ0X3N0YXRpb25fbmFtZSArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCArIGxhZ0hvdXIgKyBsYWcySG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSArIGxhZ1RyaXAsIGRhdGE9YmlrZS5wYW5lbCwgbWV0aG9kID0gImxtIiwgdHJDb250cm9sID0gZml0Q29udHJvbCwgbmEuYWN0aW9uID0gbmEucGFzcykKCnJlZy5jdiRyZXNhbXBsZSAlPiUgCiAgc3VtbWFyaXNlKE1BRSA9IG1lYW4ocmVnLmN2JHJlc2FtcGxlWywzXSksCiAgICAgICAgICAgIHNkKHJlZy5jdiRyZXNhbXBsZVssM10pCikgJT4lCiAga2JsKGNvbC5uYW1lPWMoJ01lYW4gQWJzb2x1dGUgRXJyb3InLCdTdGFuZGFyZCBEZXZpYXRpb24gb2YgTUFFJykpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKQpgYGAKCgojIENvbmNsdXNpb24KCgo=